/*
 * KeyBoard.c
 *
 *  Created on: Apr 28, 2016
 *      Author: MJY
 */
#include "headfile.h"

//按键I/O初始化
//5向开关的
//需要改接口的时候，在头文件做相应修改就行
void Key_IO_Init(){
	
	gpio_init(KeyUPin,GPI,0);
	gpio_init(KeyDPin,GPI,0);
	gpio_init(KeyLPin,GPI,0);
	gpio_init(KeyRPin,GPI,0);
	gpio_init(KeyCPin,GPI,0);	
}

/////////////////////////////////////////////  按键输入检测   ////////////////////////////////////////////////////////////////
//功能：5向开关检测按键
//返回值 ： 'U' 'D' 'L' 'R' 'C' 分别代表5向按键的 上、下、左、右、中 5个方向上的某个被按下了 ，没有任何按键按下返回 ' '
//
//     注意：
//         1、需要在头文件中配置按键的引脚，以及按下/松开按键时的电平状态
//         2、按键输入分为不按，短按单击，长按连击三种模式。可以在头文件中把短按配置成按下时返回按键值或松开的时候返回按键值
//            短按的时候在 按下/松开 的时候返回一次按键值（需要头文件配置），短按超过一段时间之后，自动进入连击模式（进入的时间
//            以及连击的速度可以在头文件中配置），连击模式下持续返回按键值
//         3、5个按键是有优先级的，分别为 ： 上 > 下 > 左 > 右 > 中
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
uint8_t GetKeyInpt(){

	//这个数组表示按键的状态，0表示已经松开，1表示短按，2表示长按进入连击模式，3表示超级连击（连击速度超快）
	static uint8_t key_state[] =   {0 , 0 , 0 , 0 , 0 };
	//                   对应的按键 'U' 'D' 'L' 'R' 'C'

	uint8_t key_input[5] = {0}; //按键当前的输入状态
#if Key_Trigger_Rising_Edge == 1
	static uint8_t key_input_1[5] = {0}; //上一次按键的输入
#endif
	static uint8_t key_con[5] = {0}; //按键计数，控制按下的持续时间  
	static uint16_t key_con_s[5] = {0};
    
    static uint8_t Key_x[] = {'U','D','L','R','C'}; //返回值
	uint8_t i = 0;

	//获取键盘状态
	key_input[0] = Key_Up;
	key_input[1] = Key_Down;
	key_input[2] = Key_Left;
	key_input[3] = Key_Right;
	key_input[4] = Key_Center;
	for(i = 0;i<5;i++){

		if(key_state[i]==0 && key_input[i] == Key_Press){//某个按键的之前状态是松开的，然后又被按下了,这是短按模式

			KEY_Delay(100); //消抖
			key_state[i] = 1; //按键状态改为按下
#if Key_Trigger_falling_Edge == 1
			return Key_x[i]; //返回按键值，在这返回相当于是下降沿触发（短按按下的时候返回值）
#elif Key_Trigger_Rising_Edge == 1
            return ' '; //上升沿在这不返回按键值
#endif
		}
		else if(key_state[i] == 1 && key_input[i] == Key_Press){ //上次某个按键被按下了，这次还是按下的，检测长按模式

			key_con[i]++;
			KEY_Delay(10);

			if(key_con[i]>=Time_Multiple_Hit){   //设置计数时间
				key_state[i] = 2; //该按键状态改为长按连击模式
				key_con[i] = 0;   //清空计数值
#if Key_Trigger_Rising_Edge == 1
				return Key_x[i];  //上升沿模式下，在进入连击模式之前先返回一次单击的按键值
#endif
			}
		}
		else if(key_state[i] == 2 && key_input[i] == Key_Press){ //如果还一直按着这个按键，进入长按模式

			key_con[i]++;
			key_con_s[i]++; //超级连击计数
			KEY_Delay(10);
			if(key_con[i]>=Speed_Multiple_Hit){   //设置计数时间
				key_con[i] = 0;   //清空计数值
				return Key_x[i]; //返回按键值
			}
			if(key_con_s[i] >= Time_Super_Multiple_Hit){

				key_con[i] = 0;   //清空计数值
				key_con_s[i] = 0;   //清空计数值
				key_state[i] = 3; //进入超级连击模式
				return Key_x[i]; //返回按键值
			}
		}
		else if(key_state[i] == 3 && key_input[i] == Key_Press){ //处于超级连击状态还按着按键

			key_con_s[i]++;
			KEY_Delay(10);
			if(key_con_s[i] >= Super_Speed_Multiple_Hit){

				key_con_s[i] = 0; //清计数值
				return Key_x[i]; //返回按键值
			}
		}
		else if(key_state[i] == 2 && key_input[i] == Key_Release){ //某按键上次状态是长按，但是这次没有按键按下

			key_state[i] = 0; //状态改为没按下
			key_con[i] = 0; //检测长按计数清0
		}
#if Key_Trigger_Rising_Edge == 1
        //这一次检测某按键是松开的，但是上一次是按下的，而且按键的状态是短按的时候，返回按键值（这个if相当于上升沿，短按松开的时候返回按键值）
        else if((key_input[i] == Key_Release && key_input_1[i] == Key_Press) && (key_state[i] == 1)){
            
            key_state[i] = 0;
            return Key_x[i]; //返回按键值
        }
#endif
		else if(key_input[i] == Key_Release){ //某按键没有按下

			key_state[i] = 0; //状态改为没按下
			key_con[i] = 0; //检测长按连击计数清0
			key_con_s[i] = 0; //检测超级连击计数清0
		} 
#if Key_Trigger_Rising_Edge == 1
        key_input_1[i] = key_input[i]; //记录按键
#endif
	}
	return ' '; //没有任何按键按下时返回空格' '
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//配置系统在线调参界面核心函数
uint8_t KeyInput = 0; //按键的输入
int8_t cursor_x = 0,cursor_y = 0; //光标的X,Y坐标
uint8_t state = 0; //调参状态 0 1 2 对应处于一级菜单 二级菜单 和二级菜单调整参数 三个状态
int8_t ParameterNo = 0; //一级菜单参数号
int8_t Parameter2No = 0; //二级菜单的参数号

//通过修改这个使能菜单数组，可以设置某个参数号下是否能有二级菜单。1表示有二级菜单，0表示没有
uint8_t en_sec_menu[16]={1,1,1,1,1,1,1,1,0,1,1 ,1 ,1 ,1 ,1 ,0 }; //使能二级菜单
//          ParameterNo: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15


//调参界面中：
//上下可以切换选中的参数
//按下左键初始化OLED（在OLED花屏之后初始化可以使小液晶回复正常显示）
//按下中心键，如果在一级菜单中且对应参数号使能了二级菜单的话，会进入到二级菜单中。在二级菜单中，如果光标为 '>' 的时候 按下中心键 会变为 '-'
//这个时候可以通过上、下键修改对应的参数，再按一次中心键 光标重新变回 '>' 参数设置完成，光标又可以通过上、下键选择参数
//如果按下右键，可以自己定义，我的程序里是启动智能车
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ConfigParameterCore(){
	
	KeyInput = GetKeyInpt(); //获取按键

//////////////////////////////// 以下是调参界面的操作逻辑设置   //////////////////////////////////////////////////////////////////////////////////////////
	if(KeyInput == 'L') OLED_Init(); //左键初始化液晶屏 防止液晶花屏

	//移动光标
	if(state == 0){ //在一级菜单中（state = 0）

		if(KeyInput == 'U') {OLED_printf(cursor_x,cursor_y," ");ParameterNo--;}
		else if(KeyInput == 'D') {OLED_printf(cursor_x,cursor_y," ");ParameterNo++;}
	}
	else if(state == 1){ //在二级菜单中

		if(KeyInput == 'U') {OLED_printf(cursor_x,cursor_y," "); Parameter2No--;}
		else if(KeyInput == 'D') {OLED_printf(cursor_x,cursor_y," ");Parameter2No++;}
	}

	//在这修改  ParameterNo 的范围可以配置哪些一级参数号拥有二级菜单 如果改为 ParameterNo < 15 则所有的一级菜单都拥有二级界面
	//有一些标志位需要经常使用，但是每次进入二级菜单又太麻烦的时候，可以留几个一级界面的位置，直接在一级界面修改标志位，而不进入二级菜单。
	if(state == 0 && en_sec_menu[ParameterNo] == 1){ //如果使能了二级菜单就进入二级菜单
		//更改状态进入二级菜单
		if(KeyInput == 'C'){state = 1;OLED_Fill(0); } //清屏
	}
	else if(state == 1){ //二级菜单中

		//显示返回键
		OLED_printf(X(2),7,"Back");

		if(Parameter2No!= 6){ //没有光标选中返回时，按下中心键进入修改参数

			if(KeyInput == 'C') state = 2; //更改状态 开始修改参数
		}
		else if(Parameter2No== 6){ //选中了返回
			if(KeyInput == 'C'){state = 0;Parameter2No = 0;OLED_Fill(0);} //返回一级菜单 清屏
		}
	}
	else if(state == 2){if(KeyInput == 'C') state = 1;} //修改参数完成后再按一下中心键确定修改
//////////////////////////////// 调参界面的操作逻辑设置结束   //////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////// 以下是通过参数号确定光标   ///////////////////////////////////////////////////////////////////////////////////////////////////
	if(state == 0){ //一级菜单的参数

		//参数号限位
		if(ParameterNo > 15) ParameterNo = 0;
		else if(ParameterNo < 0) ParameterNo = 15;
		//通过参数号确定光标坐标
		if(ParameterNo == 0) {cursor_x = 0;cursor_y = 0;}
		else if(ParameterNo == 1) {cursor_x = 0;cursor_y = 1;}
		else if(ParameterNo == 2) {cursor_x = 0;cursor_y = 2;}
		else if(ParameterNo == 3) {cursor_x = 0;cursor_y = 3;}
		else if(ParameterNo == 4) {cursor_x = 0;cursor_y = 4;}
		else if(ParameterNo == 5) {cursor_x = 0;cursor_y = 5;}
		else if(ParameterNo == 6) {cursor_x = 0;cursor_y = 6;}
		else if(ParameterNo == 7) {cursor_x = 0;cursor_y = 7;}
		else if(ParameterNo == 8) {cursor_x = 64;cursor_y = 0;}
		else if(ParameterNo == 9) {cursor_x = 64;cursor_y = 1;}
		else if(ParameterNo == 10) {cursor_x = 64;cursor_y = 2;}
		else if(ParameterNo == 11) {cursor_x = 64;cursor_y = 3;}
		else if(ParameterNo == 12) {cursor_x = 64;cursor_y = 4;}
		else if(ParameterNo == 13) {cursor_x = 64;cursor_y = 5;}
		else if(ParameterNo == 14) {cursor_x = 64;cursor_y = 6;}
		else if(ParameterNo == 15) {cursor_x = 64;cursor_y = 7;}
	}
	else if(state == 1){ //二级菜单的参数

		//参数号限位
		if(Parameter2No > 6) Parameter2No = 0;
		else if(Parameter2No < 0) Parameter2No = 6;
		//通过参数号确定光标坐标
		if(Parameter2No == 0) {cursor_x = 0;cursor_y = 1;}
		else if(Parameter2No == 1) {cursor_x = 0;cursor_y = 2;}
		else if(Parameter2No == 2) {cursor_x = 0;cursor_y = 3;}
		else if(Parameter2No == 3) {cursor_x = 0;cursor_y = 4;}
		else if(Parameter2No == 4) {cursor_x = 0;cursor_y = 5;}
		else if(Parameter2No == 5) {cursor_x = 0;cursor_y = 6;}
		else if(Parameter2No == 6) {cursor_x = 0;cursor_y = 7;}
	}
///////////////////////////////////// 光标位置确定结束 ///////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////// 显示光标  ///////////////////////////////////////////////////////////////////////////////////////////////////
	//显示光标
	if(state == 0)
		OLED_printf(cursor_x,cursor_y,">");
	else if(state == 1)
		OLED_printf(cursor_x,cursor_y,">");
	else if(state == 2)
		OLED_P6x8char(cursor_x,cursor_y,'*');
///////////////// 显示光标结束 ///////////////////////////////////////////////////////////////////////////////////////////////////

////////////////  保留功能按下右键    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	if(KeyInput == 'R') {  //按下右键

	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//通过按键修改某个参数的值
//传入参数：parameter_type ptype ：需要修改的这个参数的类型，枚举定义在头文件中
//                                int8_t，    该变量为char型
//                                uint8_t，   该变量为unsigned char型
//                                int16_t，   该变量为short型
//                                uint16_t，  该变量为unsigned short型
//                                int32_t，   该变量为int型
//                                uint32_t，  该变量为unsigned int型
//                                pfloat，    该变量为float型
//                                pdouble，   该变量为double型
//
//         void* ppara：要修改的变量
//
//         double precision ：每次加/减的精度
//
//         uint8_t keyx ：按键的输入值
//
//传出参数：无
//用法实例：ChangeParameter(pdouble,&p,0.05,KeyInput); //修改double类型变量p的值，每次修改0.05（按键输入为'U'/'D'的时候修改）
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ChangeParameterVal(parameter_type ptype,void* ppara,double precision,uint8_t keyx){

	if(keyx == 'U'){

		switch(ptype)
		{
		case pint8_t:     *(( int8_t *  )ppara) += precision;break;
		case puint8_t:    *(( uint8_t * )ppara) += precision;break;
		case pint16_t:    *(( int16_t * )ppara) += precision;break;
		case puint16_t:   *(( uint16_t *)ppara) += precision;break;
		case pint32_t:    *(( int32_t * )ppara) += precision;break;
		case puint32_t:   *(( uint32_t *)ppara) += precision;break;    
		case pfloat:      *(( float *   )ppara) += precision;break;
		case pdouble:     *(( double *  )ppara) += precision;break;
		}
	}
	else if(keyx == 'D'){

		switch(ptype)
		{
		case pint8_t:     *(( int8_t *  )ppara) -= precision;break;
		case puint8_t:    *(( uint8_t * )ppara) -= precision;break;
		case pint16_t:    *(( int16_t * )ppara) -= precision;break;
		case puint16_t:   *(( uint16_t *)ppara) -= precision;break;
		case pint32_t:    *(( int32_t * )ppara) -= precision;break;
		case puint32_t:   *(( uint32_t *)ppara) -= precision;break;    
		case pfloat:      *(( float *   )ppara) -= precision;break;
		case pdouble:     *(( double *  )ppara) -= precision;break;
		}
	}
}

//修改某标志位的值（一般用于没有二级菜单的位置）
void ChangeFlagVal(uint8_t *flag,uint8_t keyx,uint8_t min,uint8_t max){

	if(keyx == 'C')
		(*flag)++;

	if(*flag > max) *flag = min;
	else if(*flag < min) *flag = max;
}

//清除变量无效的显示
void clean_invalid_char(uint8_t endx){

	uint8_t i = 0;
	for(i = getx();i<=endx;i+=6){

		OLED_P6x8char(i,gety(),' ');
	}
}

double p = 200;
uint8_t SNM_Flag = 0;
//调参界面
void OLED_ConfigParameter(){

	ConfigParameterCore(); //调参界面的核心，负责控制光标的位置，各级菜单之间切换

/////////   显示一级菜单内容    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	if(state == 0){

		OLED_printf(X(2),0,"MOTPID");

		OLED_printf(X(13),0,"SNM_FLG:%d",SNM_Flag);
		OLED_printf(X(13),7,"FSave");
	}
/////////   显示二级菜单内容    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	else if(state == 1 || state == 2){

		switch(ParameterNo)
		{
		//PID的参数
		case 0:
			//ParameterNo = 0的二级菜单标题
			OLED_printf(X(7),0,"MOT_PID");
			//显示各个参数
			OLED_printf(X(2),1,"P=%.2f",p);clean_invalid_char(OLEDXEND);

			//修改各个参数
            if(state == 2){

            	if(Parameter2No == 0)ChangeParameterVal(pdouble,&p,5,KeyInput);
            }
			break;
		}

	}

//////////////////////  修改没有二级菜单的位置的参数   ////////////////////////////////////////////////////////////////////////
	if(state == 0){

		//没有二级菜单的位置
		if(en_sec_menu[ParameterNo] == 0){

			switch(ParameterNo)
			{
			case 8: ChangeFlagVal(&SNM_Flag,KeyInput,0,3);break;
			}
		}
	}
//////////////////////  没有二级菜单的位置的参数 结束  ////////////////////////////////////////////////////////////////////////
}

